/*
**************************************************************************************
*Myriad Engine                                                                       *
*Copyright (C) 2006-2007, 5d Studios (www.5d-Studios.com)                            *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
package leola.types;

/**
 * Base object for Leola types.
 * 
 * @author Tony
 *
 */
public abstract class LeoObject {

	/*
	 * Type
	 */
	public enum LeoType {
		  NULL
		, VOID
		, CLASS
		, MAP
		, FUNCTION
		, ARRAY
		, NATIVE_FUNCTION
		, NATIVE_CLASS
		
		, STRING
		, BOOLEAN
		, NUMBER
	}
	
	/**
	 * The underlying type
	 */
	private LeoType type;

	/**
	 * If this object can be reassigned
	 */
	private boolean isFinal;
	
	/**
	 * @param type
	 */
	public LeoObject(LeoType type) {
		this.type = type;
		this.isFinal = false;
	}
	
	/**
	 * @return the type
	 */
	public LeoType getType() {
		return type;
	}
	
	/**
	 * @return if this object has it's own scope.
	 */
	public boolean isScopedObject() {
		return false;
	}
	
	/**
	 * @return the isFinal
	 */
	public boolean isFinal() {
		return isFinal;
	}

	/**
	 * @param isFinal the isFinal to set
	 */
	public void setFinal(boolean isFinal) {
		this.isFinal = isFinal;
	}
	
	/**
	 * Determines if its one of these types
	 * @param leoTypes
	 * @return
	 */
	public boolean isOfType(LeoType leoType) {				
		return (this.type.equals(leoType));
	}

	/**
	 * Determines if its one of these types
	 * @param leoTypes
	 * @return
	 */
	public boolean isOfType(LeoType ... leoTypes) {		
		for(LeoType t: leoTypes) {
			if ( this.type.equals(t)) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Determines if its one of these types
	 * @param leoTypes
	 * @return
	 */
	public boolean isOfType(Class<? extends LeoObject> ... leoTypes) {		
		for(Class<?> c: leoTypes) {
			if ( this.getClass().equals(c)) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Up Casts the supplied {@link LeoObject}
	 * @param <T>
	 * @param obj
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T extends LeoObject> T as(LeoObject obj) {
		return (T)obj;
	}
	
	/**
	 * Up Casts this {@link LeoObject}
	 * 
	 * @param <T>
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T extends LeoObject> T as() {
		return (T)this;
	}
	
	/**
	 * Determines if the supplied object is of type TRUE.
	 * 
	 * @param obj
	 * @return
	 */
	public static boolean isTrue(LeoObject obj) {		
		boolean isTrue = (obj != null) && obj.isTrue();		
		return isTrue;
	}
	
	/**
	 * Determines if the supplied object is of type TRUE.
	 * 
	 * @param obj
	 * @return
	 */
	public boolean isTrue() {		
		boolean isTrue = (this != LeoNull.LEONULL) && (this != LeoBoolean.LEOFALSE);
		
		/* All non NULL objects are TRUE with the exception of
		 * Number 0 and type FALSE
		 */
		if ( isTrue ) {
			if ( this.isOfType(LeoType.NUMBER) ) {
				LeoNumber n = as();
				isTrue = (n.getNumber() != 0);
			}			
		}
		
		return isTrue;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return String.format("[ %s @ %s ]", this.type, super.toString());
	}
	

	
	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		LeoObject other = (LeoObject) obj;
		if (type == null) {
			if (other.type != null)
				return false;
		} else if (!type.equals(other.type))
			return false;
		return this.eq(other);
	}

	/**
	 * Determines if it equals another object.
	 * 
	 * @param other
	 * @return true if it equals
	 */
	public abstract boolean eq(LeoObject other);
	
	/**
	 * If it's not equal another object.
	 * 
	 * @param other
	 * @return
	 */
	public boolean neq(LeoObject other) {
		return ! eq(other);
	}
	
	/**
	 * If it's less than to another object
	 * @param other
	 * @return
	 */
	public abstract boolean lt(LeoObject other);
	
	/**
	 * If it's less than or equal to another object.
	 * @param other
	 * @return
	 */
	public boolean lte(LeoObject other) {
		return eq(other) || lt(other);
	}
	
	/**
	 * If it's greater than to another object
	 * @param other
	 * @return
	 */
	public abstract boolean gt(LeoObject other);
	
	/**
	 * If it's greater than or equal to another object.
	 * @param other
	 * @return
	 */
	public boolean gte(LeoObject other) {
		return eq(other) || gt(other);
	}
	
	/**
	 * @return this types value
	 */
	public abstract Object getValue();
	
	/**
	 * @return a deep copy clone of this object
	 */
	public abstract LeoObject clone();
}
